/**
 * Una instancia dcli es un cliente del sistema,y deben
 * esperar comandos de usuarios por la consola,como 
 * realizar consultas u otras operaciones sobre los
 * programas drep correspondientes, en funcion de los
 * comandos que la instancia reciba.
 *
 * @author Colmenares Carlos (06-39380)
 * @author Tough Alex (07-41604)
 */
import nanoxml.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.RemoteException;
import java.rmi.Naming;
import java.util.Enumeration;
import java.lang.IllegalStateException;
import java.net.*;
import java.io.*;

class DCLI{   
    /**
     * La direccion IPv4 de la maquina donde debe estar 
     * corriendo un programa drep, al que conectara esta 
     * instancia dcli.
     */
    private String ip_drep = "localhost";

    /**
     * El camino dentro del sistema de archivos local donde 
     * estaran los archivos iniciales que podra subir la 
     * instancia dcli, y donde se almacenaran los archivos 
     * que la instancia dcli le solicita bajar al programa 
     * drep correspondiente.
     */
    private String ldirectorio = "."; 

    /**
     * Direccion de un archivo que contendra en cada linea 
     * uno de los comandos que puede usar una instancia dcli.
     */ 
    private String archivo = "";
   
    /**
     * Nombre del usuario, que en ese momento este usando 
     * el usuario de la instancia dcli.
     */ 
    private String nombre = "guest";

    /**
     * Numero del puerto bien conocido, que un programa drep 
     * utilizara para esperar las peticiones de las instancias dcli.
     */ 
    private int puerto = 2000;

    /**
     * Constructor de una instancia dcli del sistema.
     * 
     * @param args Parametros necesarios para la construccion
     *             de una instancia dcli.
     * 
    */
    public DCLI(String[] args){	
	super();
	try{
	    parsearArgumentos(args);
	    String ins;	
	    //Buscar e invocar un objeto DRep	     
	    String name = "rmi://"+this.ip_drep+":"+Integer.toString(this.puerto)+"/drep"; 
	    //Registry registry = LocateRegistry.getRegistry(ip_drep);	   
	    DRep drep = (DRep) Naming.lookup(name);
   
            //Registrar al usuario en el DRep
            drep.registrarUsuario(this.nombre);

	    //Conexion con servidor drep inicializada.

	    //Inicializar entrada estandar
	    BufferedReader std = new BufferedReader(new InputStreamReader(System.in)); 

	    //Procesar comandos en archivo.
	    if(!this.archivo.equals("")){
		ejecutarArchivo(drep);
	    }

	    System.out.print("dcli> ");
	    while(true){
		//Procesar comandos de entrada estandar.
		ins = std.readLine().trim();
		if(ins.equals("salir")){
                    drep.desconectarDCLI(this.nombre);
		    std.close();
		    break;
		}
		try{
		    dcli_prompt(drep,ins);
		    System.out.print("dcli> ");	    
		}
		catch(RuntimeException re1){
		    System.out.println("Error inesperado"+
			    " de drep");
		}
	    }
	}
	catch(java.net.UnknownHostException unknownhost){
	    System.out.println("Direccion IP no tiene un formato valido.");
	}
	catch(java.rmi.NotBoundException nbe){
	    System.out.println("Error en busqueda de registro.");
	}
	catch(IOException ioe){   
	    System.out.println("Error de E/S.");
	}	
	catch(IllegalArgumentException iae){   
	    System.out.println("Argumento invalido.");
	}	
	catch(DMonException de){
            System.out.println("Nombre de usuario ya registrado");
        }
    }  

    /**
     * Ejecuta los comandos que esta contenido en cada
     * uno de las lineas de un archivo.
     *
     * @param drep Objeto remoto tipo DRep al cual
     *             los metodos remotos seran invocados. 
     *       
     */
    private void ejecutarArchivo(DRep drep) 
    {	
	try{
	    FileReader fr = new FileReader(this.archivo);
	    BufferedReader br = new BufferedReader(fr);
	    String ins;
	    while ((ins = br.readLine()) != null) {		
		try{
		    dcli_prompt(drep,ins.trim());	
		}
		catch(IllegalArgumentException iae){
		    System.out.println("Argumento invalido.");
		}
		catch(RuntimeException re2){
		    System.out.println("Error inesperado de drep.");
		}

	    }
	    br.close();
	    fr.close();
	}
	catch(IOException ie){   
	    System.out.println("Error de E/S.");
	}
    }

    /**
     * Ejecuta uno de los comandos que un usuario
     * puede pedir a la instancia dcli.
     *
     * @param drep Objeto remoto tipo DRep al cual
     *             los metodos remotos seran invocados.
     *           
     * @param instruccion Comando a pedir a la instancia dcli.
     *
     * @throws IllegalArgumentException Cuando uno de los archivos
     *                                  proporcionado por la linea
     *                                  de comandos no es valida.
     *
     * @throws IOException Cuando un archivo proporcionado por la 
     *                     linea de comandos tiene tamano mayor
     *                     a 2 GB.
     */
    private void dcli_prompt(DRep drep,String instruccion)
	throws IllegalArgumentException,IOException{	
	    try{
		BufferedReader br = null;
		FileReader fr = null;
		String[] comandos = instruccion.split("\\s+");
		String comando = comandos[0];	 	
		String archivo_path = "";
		String archivo = "";
		String line = "";
		int nbytes = 0;

		if(comando.equals("ldir")){
		    Utilities.ldir(this.ldirectorio);
		}

		else if(comando.equals("cat")){
		    if(comandos.length != 2){
			System.out.println("uso: cat <nomb archivo>");
		    }
		    else{
			archivo = comandos[1].trim();

			if(!this.ldirectorio.endsWith("/")){
			    archivo_path = this.ldirectorio + "/"+ archivo;	
			}
			else{
			    archivo_path = this.ldirectorio + archivo;
			}

			File file =  new File(archivo_path);
			if(!file.exists()){
			    throw new IllegalArgumentException();
			}	    
			if(!file.canRead()){
			    throw new IllegalArgumentException();
			}
			if(file.isDirectory()){
			    throw new IllegalArgumentException();
			}

			if(Utilities.perteneceDirectorio(archivo,this.ldirectorio)){
			    Utilities.cat(archivo_path);
			}
			else{
			    System.out.println(archivo + " no se "+
				    "encuentra en el directorio "+this.ldirectorio);
			}
		    }
		}

		else if(comando.equals("sube")){
		    if(comandos.length != 2){
			System.out.println("uso: sube <nomb archivo>");
		    }
		    else{
			archivo = comandos[1].trim();
			if(!this.ldirectorio.endsWith("/")){
			    archivo_path = this.ldirectorio + "/"+ archivo;	
			}
			else{
			    archivo_path = this.ldirectorio + archivo;
			}

			File file = new File(archivo_path);
			if(!file.exists()){
			    throw new IllegalArgumentException();
			}	    
			if(!file.canRead()){
			    throw new IllegalArgumentException();
			}
			if(file.isDirectory()){
			    throw new IllegalArgumentException();
			}

			//Si el archivo es muy grande, abortar
			if( file.length() > Integer.MAX_VALUE/2 ){
			    throw new IOException("Archivo muy grande! "
				    +archivo);
			} 

			FileInputStream fis = new FileInputStream(file);
			byte[] streamDeBytes = new byte[(int)file.length()];
			fis.read(streamDeBytes,0,(int)file.length());
			fis.close();

			try{	
			    drep.sube(archivo,streamDeBytes);
			}	
			catch(RemoteException re1){
			    System.out.println("Error en conexion al drep.");
			}	
		    }
		}

		else if(comando.equals("baja")){
		    
		    String nombreArchivo = comandos[1].trim();

		    try{
			byte[] archivoStream = drep.baja(nombreArchivo);

			if(archivoStream.length > Integer.MAX_VALUE/2){
			    throw new IOException("Se intentó bajar un "+
				    "archivo muy pesado");
			}

			//byte array write java
			FileOutputStream fos = new FileOutputStream(this.ldirectorio+
				"/"+nombreArchivo);

			fos.write(archivoStream);
			fos.close();
		    }
		    catch(DRepException drep1){
			    System.out.println("Archivo: "+ nombreArchivo +
						" no pudo ser abierta.");
		    }
		    catch(RemoteException re1){
			    System.out.println("Error en conexion al drep.");
		    }
		}

		else if(comando.equals("rdir")){    
		    try{
			String[] contents = drep.ldir();
			int j = 0;
			while(j< contents.length){
			    System.out.println(contents[j]);
			    j++;
			}
		    }
		    catch(RemoteException re1){
			    System.out.println("Error en conexion al drep.");
		    }
		}

		else if(comando.equals("tuser")){
		    try{
			String[] contents = drep.tuser();
			int j = 0;
			while(j< contents.length){
			    System.out.println(contents[j]);
			    j++;
			}
		    }
		    catch(RemoteException re1){
			System.out.println("Error en conexion al drep.");
		    }
		}

		else if(comando.equals("nrep")){
		    try{
			int num = drep.nrep();
			System.out.println(num);
		    }
		    catch(RemoteException re1){
			    System.out.println("Error en conexion al drep.");
		    }
		}

		else if(comando.equals("ayuda")){
		    System.out.println(
			    "Ingrese el comando que desee que sea ejecutado, los "+
			    "comandos reconocidos son los siguientes:"
			    );
		    System.out.println();
		    System.out.println(
			    "\tayuda:    Imprime este menú"        
			    );
		    System.out.println();
		    System.out.println(
			    "\tldir:     Se listarán los nombres y tamaños de cada "+
			    "uno de los archivos pertenecientes al directorio " +
			    "<ldirectorio> ."       
			    );
		    System.out.println();
		    System.out.println(
			    "\trdir:     Se listarán los nombres y tamaños de cada "+
			    "uno de los archivos pertenecientes al directorio "  +
			    "<rdirectorio> del programa drep."       
			    );
		    System.out.println();
		    System.out.println(
			    "\ttuser:   Muestra una lista de todos los usuarios " + 
			    "activos en el sistema."       
			    );
		    System.out.println();
		    System.out.println(
			    "\tnrep:     Indica el numero de programas drep que " +
			    "estan activos en el sistema."       
			    );
		    System.out.println();
		    System.out.println(
			    "\tsube <nomb archivo>:     Sube el contenido de "+ 
			    "<nomb archivo> al directorio <rdirectorio> de drep."
			    );
		    System.out.println();
		    System.out.println(
			    "\tbaja <nomb archivo>:     Baja el contenido de " + 
			    "<nomb archivo> ubicado en drep hasta el directorio " +
			    "local <ldirectorio> del programa dcli."
			    );
		    System.out.println();
		    System.out.println(
			    "\tcat <nomb_archivo>: Lista por pantalla el contenido "+
			    "del archivo local <nomb_archiv> que debe estar en "+
			    "el directorio <ldirectorio>."
			    );
		    System.out.println();
		    System.out.println(
			    "\tsalir:    Terminar el programa."
			    );
		}	
		else{
		    System.out.println( "Ingrese el comando 'ayuda'" + 
			    " (sin comillas) "+
			    "para información detallada");
		} 
	    }
	    catch(IOException ioe){
		System.out.println("Error en E/S.");
	    }
	}


    /**
     * Establece los valores por defecto de los parametros nombre,
     * ip_drep, puerto, ldirectorio y archivo contenidos en formato
     * XML en el archivo archivo_conf
     *
     * @param archivo_conf Archivo en formato XML que contiene
     *                     los valores por defecto de los parametros
     *
     * @throws IOException Cuando hay un error interno al cerrar
     *                     archivo_conf
     *
    */
    private void establecerValoresDefecto(String archivo_conf)
			throws IOException{

	try{
	    if(!archivo_conf.endsWith(".xml")){
		System.out.println("El archivo "+archivo_conf+" no es un archivo xml. Sera ignorada.");
		return;
	    } 
	    XMLElement info_conf = new XMLElement();
	    FileReader fr = new FileReader(archivo_conf);
	    info_conf.parseFromReader(fr);
	    fr.close();	
	    Enumeration children = info_conf.enumerateChildren();
	    while(children.hasMoreElements()){
		XMLElement child = new XMLElement();
		child = (XMLElement)children.nextElement();
		if(child.getName().equals("nombre")){
		    this.nombre = child.getContent().trim();
		}
		else if(child.getName().equals("hostserv")){
		    this.ip_drep = child.getContent().trim();
		}
		else if(child.getName().equals("puertoserv")){
		    this.puerto = Integer.parseInt(child.getContent().trim());
		}
		else if(child.getName().equals("directorio")){
		    this.ldirectorio = child.getContent().trim();
		}
		else if(child.getName().equals("archivo")){
		    this.archivo = child.getContent().trim();
		}
	    }
	}
	catch(FileNotFoundException fnfe){
	    System.out.println("El archivo " + archivo_conf + " no existe");
	}
	catch(XMLParseException xmlpe){
	    System.out.println("Error al hacer parsing del archivo "+archivo_conf+".Sera ignorada");
	}		
    }

    /**
     * Parsea los argumentos de entrada y asigna valores por default a
     * ellos. También valida la entrada, es decir, se chequea si los
     * valores dados son correctos.
     *
     * @param args Los argumentos del programa.
     *
     * @throws UnknownHostException Cuando ip_drep no es una direccion IPv4
     *                              o hostname valido 
     *
     */
    private void parsearArgumentos(String[] args)
	throws UnknownHostException{
	    DCLI dcli = null;
	    File file = null;
	    int i = 0;
	    while(i<args.length){
		if(args[i].equals("-h") && i+1 < args.length){	
		    this.ip_drep = args[++i];
		    InetAddress ip = InetAddress.getByName(this.ip_drep);
		}				 
		else if(args[i].equals("-p") && i+1 < args.length){	
		    this.puerto = Integer.parseInt(args[++i]);
		}   
		else if(args[i].equals("-d") && i+1 < args.length){	
		    this.ldirectorio = args[++i];
		}   
		else if(args[i].equals("-a") && i+1 < args.length){	
		    this.archivo = args[++i];
		}   
		else if(args[i].equals("-n") && i+1 < args.length){	
		    this.nombre = args[++i];
		}   
		else if(args[i].equals("-c") && i+1 < args.length){
		    String a = args[++i];
		    try{
			establecerValoresDefecto(a);    		
		    }
		    catch(IOException ioex){
			System.out.println("Error al cerrar el archivo "+a+".Abortar.");
		    }
		}
		else{
		    imprimirUso();
		}
		i++; 
	    }

	    file = new File(this.ldirectorio);
	    if(!file.exists()){
		System.out.println(this.ldirectorio + 
			" no es un directorio.");
		System.exit(1);
	    }
	    if(!file.isDirectory()){
		System.out.println(this.ldirectorio + 
			" no es un directorio.");
		System.exit(1);
	    }

	    if(!file.canRead()){
		System.err.println("error, no se puede leer" + 
			" el directorio "+ 
			file);
		System.exit(1);
	    }
	    if(!archivo.equals("")){
		file = new File(this.archivo);
		if(!file.exists()){
		    System.out.println("el archivo "+ this.archivo 
			    + "no existe.");
		    System.exit(1);
		}
		if(!file.canRead()){
		    System.err.println("error, no se puede leer"+ 
			    " el directorio "
			    +file);
		    System.exit(1);
		} 
	    }
	    if(this.puerto <= 0 || this.puerto > 65535){
		System.out.println("No existe este numero de puerto.");
		System.exit(1);
	    } 
	}

    public static void main(String[] args){	
	DCLI dcli = new DCLI(args);
    }  

    /**
     * Esta función imprime los flags que se deben proporcionar
     * al programa para su correto funcionamiento
     */
    private static void imprimirUso(){
	String mnsjError =
	    "uso: dcli [-h <host de un drep>] "+ 
	    "[-p <puerto de un drep>] "+
	    "[-d <ldirectorio>] "+
	    "[-a <archivo>] "+
	    "[-n <nombre>] "+
	    "[-c <archivo configuracion>]";
	System.out.println(mnsjError);
	System.exit(1);
    }

} 
